﻿using Bettzueche.RLLibrary.Types;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Bettzueche.DampfrossContinuum
{
    /// <summary>
    /// Konjunktive Verknüpfung der Merkmalsdimensionen. Jeder Zustand hat genau ein Merkmal je Tilingsanzahl
    /// des <see cref="LandschaftsTiling"/>. Also ein Merkmal, wenn das Landschaftsmaß eine 1-Tiling Organisation hat
    /// oder ein bis N Merkmale, wenn es eine N-Tiling Organisation hat. Distanz und Richtung sind immer 1-Tiling Organisationen.
    /// </summary>
    class FeatureCombi1 : IFeatures<UmgebungC>
    {
        #region Properties

        /// <summary>
        /// Gets the kachel wert unterteilung.
        /// </summary>
        /// <value>
        /// The kachel wert unterteilung.
        /// </value>
        public LandschaftsTiling KachelWertUnterteilung { 
            get; 
            private set; }

        /// <summary>
        /// Gets the zentrums feld wert unterteilung.
        /// </summary>
        /// <value>
        /// The zentrums feld wert unterteilung.
        /// </value>
        public ZentrumTiling ZentrumsFeldWertUnterteilung { get; private set; }

        /// <summary>
        /// Gets the entfernungs unterteilung.
        /// </summary>
        /// <value>
        /// The entfernungs unterteilung.
        /// </value>
        public EntfernungTiling EntfernungsUnterteilung { get; private set; }

        #endregion

        /// <summary>
        /// Initializes a new instance of the <see cref="FeatureCombi1"/> class.
        /// Nur das LandschaftsTiling ist variabel und kann übergeben werden.
        /// Für Zentrum, Distanz und Richtung gibt es nur Standard-Tilings.
        /// </summary>
        /// <param name="kachelTiling">The kachel tiling.</param>
        /// <exception cref="System.ArgumentNullException">kachelTiling</exception>
        public FeatureCombi1(LandschaftsTiling kachelTiling) {
            if (kachelTiling == null)
                throw new ArgumentNullException("kachelTiling");
            ZentrumsFeldWertUnterteilung = new ZentrumTiling();
            EntfernungsUnterteilung = new EntfernungTiling();
            KachelWertUnterteilung = kachelTiling;
        }


        #region IFeatures<UmgebungC>

        /// <summary>
        /// Errechnet das Feature Set (Indizes der Merkmale dieses Zustands).
        /// Gibt die Merkmalsmenge(bzw. die Merkmalsnummern), die aus den Zustandseigenschaften bspw. mittels 
        /// <c>MultiTiling</c> ermittelt werden.
        /// </summary>
        /// <param name="state">The state.</param>
        /// <returns>Merkmalindizes. IDs (Indizes) der Merkmale, die zu den übergebenen Zustandsdaten gehören.</returns>
        /// <exception cref="System.NullReferenceException">if state or its properties are null</exception>.
        public int[] GetFeatureSet(UmgebungC state) {
            List<int> oobIndizes = new List<int>(); // indizes des Zwischenergebnis-Vektors mit OutOfBounds
            int anzNachbarn = state.Nachbarn.Length; // = config.Abschnitte;
            int anzZielrichtungen = state.Nachbarn.Length; // = config.Zielrichtungen;
            double[] unebenheiten = state.GetMeasures().ToArray();

            // Je Tiling eines MultiTiling gibt es ein Merkmal.
            // Note: Bei MultiTiling kann es mehrere Merkmale je Zustand geben. Hier nur MultiTiling für LandschaftsTiling.
            int[] featureNums = new int[KachelWertUnterteilung.TilingCount];
            // Beginne mit Unebenheitsmerkmal des Zentrumfeldes:
            int ztrmNum   = ZentrumTiling.GetFeatureNumber(unebenheiten[unebenheiten.Length-1]);
            // Das ZentrumTiling gibt nur ein Merkmal - es ist ein Single-Tiling. Der Wert muss
            // auf alle Merkmale des FeatVec angewendet werden:
            if (ztrmNum == Tiling.OutOfBounds)
                return new int[0];
            for (int i = 0; i < featureNums.Length; i++) {
                featureNums[i] = ztrmNum;
            }
            int indexOffset = ZentrumTiling.TileCount / ZentrumTiling.TilingCount; // erster OffsetFaktor = Anzahl möglicher Merkmale für Zentrum = 2
            int anzUIntervalleJeTiling = KachelWertUnterteilung.TileCount / KachelWertUnterteilung.TilingCount;
            // zu jedem Nachbar-Abschnitt Unebenheits-Merkmale ermitteln
            for (int abschnittsNr = 0; abschnittsNr < anzNachbarn; abschnittsNr++) {
                // Merkmale gemäß Tilings-Organisation:
                double u_ab = unebenheiten[abschnittsNr];
                int[] merkmale_u_ab = KachelWertUnterteilung.GetFeatureNumbers(u_ab);
                // MerkmalIndizes anpassen gemäß Merkmale je Abschnitt:
                for (int i = 0; i < merkmale_u_ab.Length; i++) {
                    if (featureNums[i] == Tiling.OutOfBounds)
                        continue;
                    if (merkmale_u_ab[i] == Tiling.OutOfBounds) {
                        featureNums[i] = Tiling.OutOfBounds;
                        oobIndizes.Add(i);
                    }
                    else {
                        merkmale_u_ab[i] *= indexOffset;
                        featureNums[i] += merkmale_u_ab[i];
                    }
                }
                indexOffset *= KachelWertUnterteilung.TileCount/*anzUIntervalleJeTiling*/; // Offset für nächste Kachel mit Anz-U-Werte multiplizieren, ich glaub das ist falsch! muss * TileCount
            }
            // Merkmal Richtung (einmal in Kombination mit jedem uTiling)
            int richtungNr = (int)state.RichtungZiel;
            richtungNr *= indexOffset;
            for (int i = 0; i < featureNums.Length; i++) {
                if (featureNums[i] != Tiling.OutOfBounds) {
                    featureNums[i] += richtungNr;
                }
            }
            indexOffset *= 6; // Anzahl Richtungen = 6; 1 "Tiling" 

            // Merkmal Entfernung; auch hier nur 1 "Tiling", kombiniert mit jedem uTiling
            int entfNr = EntfernungTiling.GetFeatureNumber(state.Entfernung);
            if (entfNr == Tiling.OutOfBounds)
                return new int[0];
            entfNr *= indexOffset;
            for (int i = 0; i < featureNums.Length; i++) {
                if (featureNums[i] != Tiling.OutOfBounds) {
                    featureNums[i] += entfNr;
                }
            }
            //indexOffset *= EntfernungTiling.TileCount / EntfernungTiling.TilingCount;

            // falls ein IndexOutOfBounds vorhanden, Ergebnisvektor reduzieren:
            if (oobIndizes.Count > 0) {
                List<int> resultVec = new List<int>(featureNums.Length);
                for (int i = 0; i < featureNums.Length; i++) {
                    if (featureNums[i] != Tiling.OutOfBounds) {
                        resultVec.Add(featureNums[i]);
                    }
                }
                return resultVec.ToArray();
            }
            else
                return featureNums;
        }

        public UmgebungC GetStateFromFeatures(int[] features) {
            UmgebungC retVal = new UmgebungC(features);

            // theoretisch sollte das Setzen der FeaturedID und MERKMAL-Indizes für meine Zwecke reichen !(?)!
            // Zur Berechnung des MSE muss ich dem Agenten die "DebugStates" und Sollwerte übergeben.
            // Diese DebugStates werden als Parameter der Q-Funktion (indexer) übergeben, um den Wert Q[state,action]
            // mit dem Sollwert zu vergleichen. Der Q[state,action] Indexer ruft state.GetFeatures auf, der wiederum
            // die in state gespeicherten MERKMAL-Indizes wiedergibt (um die interne Datenbasis zu referenzieren). 
            // TADA! Alles gut.
            return retVal;            
        }
        /*
            int[] merkmale = retVal.GetFeatures().ToArray();
            // jetzt umgekehrt aus fID die zugehörigen Objekte erzeugen (siehe GibMerkmalsVektor(state)):
            double[] measures = new double[7]; // Maße für Nachbarn + Zentrum
            for (int m = 0; m < merkmale.Length; m++) { // ACHTUNG!!! Mehrere Merkmale noch nicht wirklich unterstützt!! merkmale.Length=1 erwartet
                int fTemp = merkmale[m];
                // Entfernung
                int offSet = 2 * (int)Math.Pow(KachelWertUnterteilung.TileCount, 6) * 6;
                int entfNr = fTemp / offSet;
                var entfDic = EntfernungTiling.GetMedians(new int[] { entfNr });
                retVal.Entfernung = (int)entfDic[entfNr]; // abgerundet
                fTemp = fTemp - (entfNr * offSet);
                // Richtung
                offSet /= 6;
                int dirNum = fTemp / offSet;
                retVal.RichtungZiel = (Bettzueche.Dampfross.HexRichtungen)dirNum;
                fTemp = fTemp - (dirNum * offSet);
                // Nachbar SO, S, ..., N, NO
                for (int i = 0; i < 6; i++) {
                    offSet /= KachelWertUnterteilung.TileCount;
                    int kachel_iNr = fTemp / offSet;
                    var kachelDic = KachelWertUnterteilung.GetMedians(new int[] { kachel_iNr });
                    measures[i] += kachelDic[kachel_iNr]; // TODO: Bei echtem MultiTiling stattdessen Mittelwert der Mittelwerte?
                    // hier wirds kompliziert.
                    // aus dem measure müssten RiverFlags, Typ und measurebase ermittelt werden...
                    //KachelCContent content_i = new KachelCContent(4.2, Kacheltyp.Berg, Rivers.NordOst | Rivers.Nord);
                    //KachelC kachel_i = new KachelC { Content = content_i };
                    // für state.GetFeatures (wie in QEFunction[state,action] genutzt), reicht es allerdings wenn
                    // _merkmale gesetzt ist!
                    fTemp = fTemp - (kachel_iNr * offSet);
                }
                // Zentrumsfeld
                int zentrumNr = fTemp;
                var ztrmDic = ZentrumTiling.GetMedians(new int[] { zentrumNr });
                measures[measures.Length-1] = ztrmDic[zentrumNr];
            }
            for (int i = 0; i < 6; i++) {
                measures[i] /= merkmale.Length;
            }
            */

        #endregion

        public override string ToString() {
            return KachelWertUnterteilung.ToString();
        }
    }
}
